﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Windows;
using System.Windows.Data;

namespace PropertyExpression.TypedDataBinding.Wpf
{
    public interface IBinder<TData>
    {
        void Add0<TControlProperty, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Expression<Func<TData, TEndProperty>> dataPropertyFunc);

        void Add1<TControlProperty, TDataProperty, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Expression<Func<TData, TDataProperty>> dataPropertyFunc,
            Func<TDataProperty, TEndProperty> convertFunc);

        void Add2<TControlProperty, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Expression<Func<TData, TEndProperty>> dataPropertyFunc);

        ClassXxx<TData> Add3<TControlProperty, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Func<TData, TEndProperty> dataPropertyFunc);

        void Add4<TControlProperty, TDataContainer, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Func<ICopyist<TData>, TDataContainer> dataContainerCreator,
            Func<TDataContainer, TEndProperty> converFunc);

        void Add5<TControlProperty, TDataContainer, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Func<ClassXxx2<TData>, ClassXxx3<TData, TDataContainer>> dataContainerCreator,
            Func<TDataContainer, TEndProperty> converFunc);
    }

    public class Binder<TData> : IBinder<TData>
    {
        public Func<TData> Source { get; set; }

        public void Add0<TControlProperty, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Expression<Func<TData, TEndProperty>> dataPropertyFunc)
        {
            New().Add0(controlPropertyFunc, dataPropertyFunc);
        }

        public void Add1<TControlProperty, TDataProperty, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Expression<Func<TData, TDataProperty>> dataPropertyFunc,
            Func<TDataProperty, TEndProperty> convertFunc)
        {
            New().Add1(controlPropertyFunc, dataPropertyFunc, convertFunc);
        }

        public void Add2<TControlProperty, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Expression<Func<TData, TEndProperty>> dataPropertyFunc)
        {
            New().Add2(controlPropertyFunc, dataPropertyFunc);
        }

        public ClassXxx<TData> Add3<TControlProperty, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Func<TData, TEndProperty> dataPropertyFunc)
        {
            return New().Add3(controlPropertyFunc, dataPropertyFunc);
        }

        public void Add4<TControlProperty, TDataContainer, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Func<ICopyist<TData>, TDataContainer> dataContainerCreator,
            Func<TDataContainer, TEndProperty> converFunc)
        {
            New().Add4(controlPropertyFunc, dataContainerCreator, converFunc);
        }

        public void Add5<TControlProperty, TDataContainer, TEndProperty>(
            Expression<Func<TControlProperty>> controlPropertyFunc,
            Func<ClassXxx2<TData>, ClassXxx3<TData, TDataContainer>> dataContainerCreator,
            Func<TDataContainer, TEndProperty> converFunc)
        {
            New().Add5(controlPropertyFunc, dataContainerCreator, converFunc);
        }

        public MultiBinding GetBinding<TDataContainer, TEndProperty>(
            Func<ICopyist<TData>, TDataContainer> dataContainerCreator, Func<TDataContainer, TEndProperty> converFunc)
        {
            var copyist = new Copyist();
            dataContainerCreator(copyist);
            var result = new MultiBinding();
            foreach (string s in copyist.List)
            {
                var binding = new Binding
                               {
                                   Path = new PropertyPath(s)
                               };
                if (Source != null)
                {
                    binding.Source = Source();
                }
                result.Bindings.Add(
                    binding);
            }
            result.Converter = new ConverterImpl<TDataContainer, TEndProperty>(dataContainerCreator, converFunc);
            return result;
        }

        public IBinder<TData> New(MultiBinding multiBinding)
        {
            throw new NotImplementedException(); //QDO: implement this
        }

        private IBinder<TData> New()
        {
            return New(new MultiBinding());
        }

        private class ConverterImpl<TDataContainer, TEndProperty> : IMultiValueConverter
        {
            private readonly Func<TDataContainer, TEndProperty> converFunc;
            private readonly Func<ICopyist<TData>, TDataContainer> dataContainerCreator;

            public ConverterImpl(Func<ICopyist<TData>, TDataContainer> dataContainerCreator,
                                 Func<TDataContainer, TEndProperty> converFunc)
            {
                this.dataContainerCreator = dataContainerCreator;
                this.converFunc = converFunc;
            }

            public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            {
                return converFunc(dataContainerCreator(new Copyist2(values)));
            }

            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }

            private class Copyist2 : ICopyist<TData>
            {
                private readonly object[] values;

                private int index;

                public Copyist2(object[] values)
                {
                    this.values = values;
                }

                public TDataProperty Reg<TDataProperty>(Expression<Func<TData, TDataProperty>> expression)
                {
                    var result = (TDataProperty)values[index];
                    index++;
                    return result;
                }
            }
        }

        private class Copyist : ICopyist<TData>
        {
            public readonly List<string> List = new List<string>();

            public TDataProperty Reg<TDataProperty>(Expression<Func<TData, TDataProperty>> expression)
            {
                List.Add(((MemberExpression)expression.Body).Member.Name);
                return default(TDataProperty);
            }
        }
    }

    public class ClassXxx2<TData>
    {
        public ClassXxx3<TData, TDataContainer> Reg<TDataContainer>(Func<TDataContainer> func)
        {
            throw new NotImplementedException();
        }
    }

    public class ClassXxx3<TData, TDataContainer>
    {
        public ClassXxx3<TData, TDataContainer> Add<TDataProperty, TDataContainerProperty>(
            Expression<Func<TData, TDataProperty>> dataPropertyFunc,
            Expression<Func<TDataContainer, TDataContainerProperty>> dataContainerPropertyFunc)
        {
            throw new NotImplementedException();
        }
    }

    public class ClassXxx<TData>
    {
        public ClassXxx<TData> Add<TDataProperty>(
            Expression<Func<TData, TDataProperty>> dataPropertyFunc)
        {
            throw new NotImplementedException();
        }
    }

    public interface ICopyist<TData>
    {
        TDataProperty Reg<TDataProperty>(Expression<Func<TData, TDataProperty>> expression);
    }
}
